/*
Copyright 2012 Gary Bortosky

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Bortosky.UriQueryTranslation {
	/// <summary>
    /// Translates query values for a URI
	/// See http://www.ietf.org/rfc/rfc2396.txt
	/// </summary>
	public static class UriQueryTranslator {

        private static char _amper = '&';

        private static Dictionary<KeyValuePair<String, String>, String> _emptyDictionary = new Dictionary<KeyValuePair<String, String>, String>();

        /// <summary>
        /// Translates an address using the supplied new values
        /// </summary>
        /// <param name="address">The address to translate</param>
        /// <param name="newValues">A dictionary of new values</param>
        /// <returns>The translated address</returns>
        public static string Translate(string address, IDictionary<KeyValuePair<String, String>, String> newValues)
        {
            return Translate(address, ResultFromIDictionary(newValues));
        }

        /// <summary>
        /// Translates an address using the supplied new value function
        /// </summary>
        /// <param name="address">The address to translate</param>
        /// <param name="newValue">A function to return the new value for a KeyValuePair instance</param>
        /// <returns>The translated address</returns>
        public static string Translate(string address, Func<KeyValuePair<String, String>, Result> newValue)
        {
            UriParts parts;
            return (UriParts.TryParse(address, out parts)) ? Translate(parts, newValue) : address;
        }

        /// <summary>
        /// Translates a UriParts instance using the supplied new values
        /// </summary>
        /// <param name="parts">The parts of the address to translate</param>
        /// <param name="newValues">A dictionary of new values</param>
        /// <returns>The translated address</returns>
        public static string Translate(UriParts parts, IDictionary<KeyValuePair<String, String>, String> newValues)
        {
            return Translate(parts, ResultFromIDictionary(newValues));
        }

        /// <summary>
        /// Translates a UriParts instance using the supplied new value function
        /// </summary>
        /// <param name="parts">The parts of the address to translate</param>
        /// <param name="newValue">A function to return the new value for a KeyValuePair instance</param>
        /// <returns>The translated address</returns>
        public static string Translate(UriParts parts, Func<KeyValuePair<String, String>, Result> newValue)
        {
            parts.Query = parts.Query == null 
                ? null 
                : CreateQuery(TranslateKeyValuePairs(parts.QueryKeyValuePairs(), newValue));
            return parts.ToString();
        }

        /// <summary>
        /// Translates a sequence of KeyValuePair instances using the supplied new values
        /// </summary>
        /// <param name="input">The sequence of KeyValuePair instances to translate</param>
        /// <param name="newValues">A dictionary of new values</param>
        /// <returns>The translated KeyValuePair</returns>
        public static IEnumerable<KeyValuePair<String, String>> TranslateKeyValuePairs(IEnumerable<KeyValuePair<String, String>> input, IDictionary<KeyValuePair<String, String>, String> newValues)
        {
            var reducedKvps = ReduceKeyValuePairs(newValues);

            var dictionaryResult = new Func<KeyValuePair<string, string>, Result>(kvp => 
                {
                    string foundValue;
                    var valueFound = reducedKvps.TryGetValue(ReducedKeyValuePairKey(kvp), out foundValue);
                    return new Result { DoReplace = valueFound, NewValue = foundValue };
                });

            foreach (var kvp in TranslateKeyValuePairs(input, dictionaryResult))
            {
                yield return kvp;
            }
        }

        /// <summary>
        /// Translates a sequence of KeyValuePair instances using the supplied evaluator
        /// </summary>
        /// <param name="input"></param>
        /// <param name="newValue"></param>
        /// <returns></returns>
        public static IEnumerable<KeyValuePair<String, String>> TranslateKeyValuePairs(IEnumerable<KeyValuePair<String, String>> input, Func<KeyValuePair<String, String>, Result> newValue)
        {
            foreach (var kvp in input)
            {
                var lookup = newValue(kvp);
                yield return new KeyValuePair<String, String>(kvp.Key, lookup.DoReplace ? lookup.NewValue : kvp.Value);
            }
        }

        /// <summary>
        /// Creates a query part from a sequence of KeyValuePair instances.
        /// If the Value corresponding to a given key is null, the key will be by used alone.
        /// Key values are URI escaped.
        /// </summary>
        /// <param name="keyValuePairs">The key KeyValuePairs to use</param>
        /// <returns>A query part for use in an address</returns>
        public static string CreateQuery(IEnumerable<KeyValuePair<String, String>> keyValuePairs)
        {
            var count = 0;
            var builder = new StringBuilder();
            foreach (var qpart in keyValuePairs.Select(q => q.Value == null ? q.Key : String.Format("{0}={1}", q.Key, Uri.EscapeDataString(Uri.UnescapeDataString(q.Value)))))
            {
                if (count++ > 0)
                {
                    builder.Append(_amper);
                }
                builder.Append(qpart);
            }
            return builder.ToString();
        }

        private static Func<KeyValuePair<String, String>, Result> ResultFromIDictionary(IDictionary<KeyValuePair<String, String>, String> newValues)
        {
            if (newValues == null)
            {
                return new Func<KeyValuePair<string, string>, Result>(kvp => new Result { DoReplace = false });
            }
            var reducedKvps = ReduceKeyValuePairs(newValues);

            return new Func<KeyValuePair<string, string>, Result>(kvp =>
            {
                string foundValue;
                var valueFound = reducedKvps.TryGetValue(ReducedKeyValuePairKey(kvp), out foundValue);
                return new Result { DoReplace = valueFound, NewValue = foundValue };
            });
        }

        private static IDictionary<String, String> ReduceKeyValuePairs(IDictionary<KeyValuePair<String, String>, String> input)
        {
            var newDictionary = new Dictionary<String, String>(input.Count(), StringComparer.OrdinalIgnoreCase);
            foreach (var nv in input)
            {
                newDictionary.Add(ReducedKeyValuePairKey(nv.Key), nv.Value);
            };
            return newDictionary;
        }

        private static string ReducedKeyValuePairKey(KeyValuePair<String, String> kvp)
        {
            return kvp.ToString();
        }
	}
}